Skip to content

Experimental autodetection of 64 bit timespec on 32 bit platforms - #18

Closed
diwic wants to merge 2 commits into
masterfrom
66-timespec
Closed

Experimental autodetection of 64 bit timespec on 32 bit platforms#18
diwic wants to merge 2 commits into
masterfrom
66-timespec

Conversation

@diwic

@diwic diwic commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Exploits the fact (?) that even though the fields are 64 bit, only 32 of the bits are used in practice:

  • tv_nsec is less than 32 bits because there are 1 000 000 000 nanosecs in a second
  • tv_sec is less than 32 bits because audio stream length (or system uptime) is lower than 68 years

Comment thread src/lib.rs Outdated
impl timespec {
fn is64(&self) -> bool { self.0[2] != 0 || self.0[3] != 0 }
pub fn tv_sec(&self) -> i64 { self.0[0] as i64 }
pub fn tv_nsec(&self) -> i64 { if self.is64() { (self.0[2] + self.0[3]) as i64 } else { self.0[1] as i64 } }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.0[2] + self.0[3] is a nice trick and works because either field is just padding. It's a trick though. For clarity how would you feel about gating on endianness?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roderickvd Having thought about it, I think we should apply this fix on little endian only. Big endian is incredibly uncommon.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As uncommon as it may be, wouldn't it be nice for alsa-sys to work regardless?

Comment thread src/lib.rs Outdated

#[cfg(target_pointer_width = "32")]
#[derive(Copy, Clone, Eq, PartialEq, Default)]
#[repr(C)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also do #[repr(C, align(8))] to ensure it doesn't segfault when ALSA tries to write? Rust may not guarantee the same alignment.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And good idea to use those native-width i64s for alignment 😄

Comment thread src/lib.rs Outdated
use libc::{FILE, pid_t, pollfd, timeval};

#[cfg(target_pointer_width = "64")]
#[derive(Copy, Clone, Eq, PartialEq, Default)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug may be nice to implement.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your rewrite I think these were dropped altogether (because they can't be derived on the union). That creates an asymmetry with libc::timespec, which does derive them.

Comment thread src/lib.rs Outdated
#[cfg(target_pointer_width = "32")]
impl timespec {
fn is64(&self) -> bool { self.0[2] != 0 || self.0[3] != 0 }
pub fn tv_sec(&self) -> i64 { self.0[0] as i64 }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If is64 we should retain the upper half:

pub fn tv_sec(&self) -> i64 {
    if self.is64() {
        #[cfg(target_endian = "little")]
        ((self.0[1] as i64) << 32) | (self.0[0] as u32 as i64)
        #[cfg(target_endian = "big")]
        ((self.0[0] as i64) << 32) | (self.0[1] as u32 as i64)
    } else {
        self.0[0] as i64
    }
}

...I think 🤯 time problems are hard.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roderickvd Yeah and there is an edge case where is64 would return false because tv_nsec actually is 0. This would fail on big endian :-(

@diwic

diwic commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

@roderickvd Based on your review I made another attempt. Let me know what you think about it. The way it's written I don't think it needs any cfg on target_endian.

@diwic
diwic requested review from Copilot and roderickvd and removed request for roderickvd June 23, 2026 17:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR attempts to make snd_htimestamp_t (timespec) work on 32-bit platforms regardless of whether the underlying ALSA/libc uses a 32-bit or 64-bit struct timespec, by introducing a custom timespec type with runtime “autodetection”.

Changes:

  • Replaced the direct libc::timespec import with a crate-defined timespec type.
  • Added tv_sec() / tv_nsec() accessor methods and a Default impl for the new timespec.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib.rs
Comment on lines +10 to +34
#[derive(Copy, Clone)]
#[repr(C)]
pub union timespec {
t: libc::timespec,
t64: [i64; 2],
}

#[cfg(not(target_pointer_width = "32"))]
impl timespec {
#[inline(always)]
pub fn tv_sec(&self) -> i64 { unsafe { self.t.tv_sec as i64 } }
#[inline(always)]
pub fn tv_nsec(&self) -> i64 { unsafe { self.t.tv_nsec as i64 } }
}

#[cfg(target_pointer_width = "32")]
impl timespec {
unsafe fn is64(&self) -> bool { self.t64[1] != 0 }
pub fn tv_sec(&self) -> i64 { unsafe { if self.is64() { self.t64[0] } else { self.t.tv_sec as i64 } } }
pub fn tv_nsec(&self) -> i64 { unsafe { if self.is64() { self.t64[1] } else { self.t.tv_nsec as i64 } } }
}

impl Default for timespec {
fn default() -> Self { unsafe { zeroed() } }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LLM has got a point, but the proposed fix would just recreate the segfault that we're trying to address.

@roderickvd roderickvd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union is a great find, would never have thought of that. It doesn't solve the problem of misdetection on big-endian 32 bit though, and Copilot has got a point w.r.t. to the other methods.

As an alternative I submitted #19 with a build probe that should have none of these side-effects, what do you think?

@diwic diwic closed this Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants